﻿using System;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace AMEE.BeinControl
{
    class Sender : IDisposable
    {
        #region Constants

        /// <summary>Start symbol ($) of a MCU message</summary>
        public const char START = '$';
        /// <summary>Delimiter symbol (:) of a MCU message</summary>
        public const char DELIM = ':';
        /// <summary>End symbol (;) of a MCU message</summary>
        public const char END = ';';

        #endregion

        #region Fields

        bool _disposed = false;
        TcpClient _client;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the ID (IP-Subaddress+port).
        /// </summary>
        /// <value>
        /// The ID.
        /// </value>
        public String ID { get; private set; }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Sender"/> class.
        /// </summary>
        /// <param name="ip">The ip.</param>
        /// <param name="port">The port.</param>
        /// <exception cref="System.Net.Sockets.SocketException">An error occurred when accessing the socket.</exception>
        /// <exception cref="AMEE.BeinControl.BeinControlException"> Invalid port number. or Invalid IP adress format. </exception>
        public Sender(string ip, int port) {
            checkCtorParams(ip, port); // throws Exc
            _client = new TcpClient(ip, port);
            this.ID = ip.Substring(ip.LastIndexOf('.')) + "/" + port;
        }
                

        ~Sender()
        {
            Dispose(false);
        }

        #endregion


        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        /// <summary>
        /// Writes the specified value.<para>
        /// "$:a:b:c:d;" with a,b,c,d as 4 digit number</para>
        /// </summary>
        /// <param name="a">A.</param>
        /// <param name="b">The b.</param>
        /// <param name="c">The c.</param>
        /// <param name="d">The d.</param>
        /// <returns>true, if send. false otherwise</returns>
        public bool Write(int a, int b ,int c, int d) {
            if (a < 0 || a > 9999) {
                Debug.WriteLine(this.ID + " Sender.Write(int,int,int,int) bad parameter a: " + a, "Warning");
                return false;
            }
            if (b < 0 || b > 9999) {
                Debug.WriteLine(this.ID + " Sender.Write(int,int,int,int) bad parameter b: " + b, "Warning");
                return false;
            }
            if (c < 0 || c > 9999) {
                Debug.WriteLine(this.ID + " Sender.Write(int,int,int,int) bad parameter c: " + c, "Warning");
                return false;
            }
            if (d < 0 || d > 9999) {
                Debug.WriteLine(this.ID + " Sender.Write(int,int,int,int) bad parameter d: " + d, "Warning");
                return false;
            }
            string msg = String.Format("{0}{1}D{1}{3:D4}{1}{4:D4}{1}{5:D4}{1}{6:D4}{2}", Sender.START, Sender.DELIM, Sender.END, a, b, c, d);
            Debug.WriteLine(this.ID + "Sender.Write(int,int,int,int) - sending " + msg, "Info");
            return this.Write(msg);
        }

        /// <summary>
        /// Sends the specified message to the MCU.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns>true, if send. false otherwise</returns>
        public bool Write(String message) {
            if (_client == null) {
                Debug.WriteLine(this.ID + " Sender.Write - client is null", "Warning");
                return false;
            }
            if (!_client.Connected) {
                Debug.WriteLine(this.ID + " Sender.Write - client not connected!", "Warning");
                return false;
            }
            using (NetworkStream netStrm = _client.GetStream()) {
                using (StreamWriter writer = new StreamWriter(netStrm, Encoding.ASCII)) {
                    writer.AutoFlush = true;
                    try {
                        writer.Write(message);
                        Debug.WriteLine(this.ID + " Message sent.", "Info");
                    }
                    catch (Exception ex) {
                        Debug.WriteLine(this.ID + " Sender.Write - Exception", "Error");
                        Debug.WriteLine(ex.Message);
                        return false;
                    }
                }
            }
            return true;
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be disposed.
        protected virtual void Dispose(bool disposing) {
            // Check to see if Dispose has already been called.
            if (!this._disposed) {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing) {
                    // Dispose managed resources.
                    if (_client != null)
                        _client.Close();
                }

                // Note disposing has been done.
                _disposed = true;

            }
        }

        #region Helpers

        /// <summary>
        /// Checks the ctor params.
        /// </summary>
        /// <param name="ip">The ip.</param>
        /// <param name="port">The port.</param>
        /// <exception cref="AMEE.BeinControl.BeinControlException">
        /// Invalid port number.
        /// or
        /// Invalid IP adress format.
        /// </exception>
        void checkCtorParams(string ip, int port) {
            String regex = "\\b(\\d{1,3}\\.){3}\\d{1,3}\\b";
            Match match = Regex.Match(ip, regex);
            if (match.Success) {
                if (port < 1)
                    throw new BeinControlException("Invalid port number.", new ArgumentException("invalid port number", "port"));
            }
            else
                throw new BeinControlException("Invalid IP adress format.", new ArgumentException("invalid (IPv4) format", "ip"));
        }

        #endregion
    }
}
